﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VulnDBExplorer.Services;
using System.Collections;
using System.IO;
using TextDataExtractor.DEML;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Luna;

namespace VulnDB.Action.DataCollectors.Services.CodeAge {
	public class CodeProfile : ICodeProfile {

		public static readonly Regex CVSLinePattern = new Regex(@"^(\d+(\.\d+)+) +\((.+?) (\d+-[a-zA-Z]+-\d+)\): (.+?)$", RegexOptions.Multiline | RegexOptions.Compiled);
		public static readonly Regex HgLinePattern = new Regex(@"(\w+).+?(\d+) \w+ (\w+ \d+) .+? (\d+) .+? (.+?):(\d+):", RegexOptions.Compiled | RegexOptions.Multiline);

		public CodeProfile(ICodebase codebase, ISourceProfileStorage storage) {
			this.Codebase = codebase;
			this.Storage = storage;
		}

		public ICodebase Codebase { get; private set; }

		public ISourceProfileStorage Storage { get; private set; }

		public ISourceFileProfile this[string filename] {
			get { return Storage != null ? Storage.loadProfile(filename) : null; }
		}

		public void processHgAnnotatedFile(StreamReader input, IProgressMonitor monitor) {
			Stopwatch watch = new Stopwatch();
			try {
				if (monitor != null)
					monitor.start("Profiling code base " + Codebase.Name, (int)input.BaseStream.Length);

				watch.Start();
				SourceFileProfile profile = null;
				bool skipFile = false;

				while (!input.EndOfStream) {
					string line = input.ReadLine();
					Match m = HgLinePattern.Match(line);
					if (m.Success) {
						string author = m.Groups[1].Value;
						string revision = m.Groups[2].Value;
						DateTime age = DateTime.Parse(m.Groups[3].Value + " " + m.Groups[4].Value);
						string filename = m.Groups[5].Value.Trim();
						int lineNo = m.Groups[6].Value.toInt();
						if (profile == null || !profile.Filename.Equals(filename)) {
							skipFile = Storage.hasProfile(filename);
							if (!skipFile) {
								if (profile != null) {
									Storage.writeProfile(profile);
									if (monitor != null) {
										TimeSpan elapsed = watch.Elapsed;
										long curPos = input.BaseStream.Position > 0 ? input.BaseStream.Position : 1;

										long remaintTick = elapsed.Ticks * (input.BaseStream.Length - curPos) / curPos;

										monitor.update((int)input.BaseStream.Position, (int)input.BaseStream.Length);
										monitor.Description = string.Format("Ellapsed: {0}. Remain estimated: {1}", elapsed.ToString().Substring(0, 8), new TimeSpan(remaintTick).ToString().Substring(0, 8));
									}
								}
								profile = new SourceFileProfile(filename);
							}
						}
						if (!skipFile) profile.addLine(revision, author, age, lineNo);
					}
				}
			}
			finally {
				if (monitor != null)
					monitor.stop();
			}
		}

		public void processAnnotatedFile(StreamReader input, AbstractPattern pat, IProgressMonitor monitor) {
			Stopwatch watch = new Stopwatch();
			try {
				pat.Registry.Event += skipFileWhenNeeded;
				if (monitor != null)
					monitor.start("Profiling code base " + Codebase.Name, (int)input.BaseStream.Length);
				
				string buffer = string.Empty;
				ExecutionContext context = new ExecutionContext(null, "root");
				int maxFileCount = int.MaxValue;

				watch.Start();
				while (maxFileCount > 0 && !input.EndOfStream && pat.match(input, ref buffer) > 0) {
					Match m = CVSLinePattern.Match(pat.MatchedText);
					string filename = (string)(pat as Section).StartLocator.getDataItem(1);
					SourceFileProfile profile = new SourceFileProfile(filename);
					int lineidx = 1;
					while (m.Success) {
						profile.addLine(m.Groups[1].Value, m.Groups[3].Value,
							DateTime.Parse(m.Groups[4].Value), lineidx);
						lineidx++;
						m = m.NextMatch();
					}
					Storage.writeProfile(profile);
					
					//pat.performExtraction(context);
					//foreach (IDataStorage file in context.enumerateObject("FILE")) {
					//    SourceFileProfile profile = new SourceFileProfile((string)file.getDataItem("filename"));
					//    IList lines = (IList)file.getDataItem("lines");
					//    int lineIdx = 1;
					//    foreach (var item in lines)
					//        if (item is IDataStorage) {
					//            IDataStorage revision = (IDataStorage)item;
					//            lineIdx++;
					//            profile.addLine(
					//                (string)revision.getDataItem("rev"), (string)revision.getDataItem("author"),
					//                (DateTime)revision.getDataItem("age"), lineIdx);
					//        }
					//    Storage.writeProfile(profile);
					//    maxFileCount--;
					//}
					
					if (monitor != null) {
						TimeSpan elapsed = watch.Elapsed;
						long curPos = input.BaseStream.Position > 0 ? input.BaseStream.Position : 1;

						long remaintTick = elapsed.Ticks * (input.BaseStream.Length - curPos + buffer.Length) / curPos;

						monitor.update((int)input.BaseStream.Position, (int)input.BaseStream.Length);
						monitor.Description = string.Format("Ellapsed: {0}. Remain estimated: {1}", elapsed.ToString().Substring(0,8), new TimeSpan(remaintTick).ToString().Substring(0,8));
					}
				}
			}
			finally {
				pat.Registry.Event -= skipFileWhenNeeded;
				if (monitor != null)
					monitor.stop();
			}
		}

		private void skipFileWhenNeeded(object sender, DemlEventArg arg) {
			if (arg.Event == DemlEvent.BeginMatch && arg.Element is Section && arg.Element.getArgument("for").Equals("annotated-file")) {
				Section sec = (arg.Element as Section);
				string filename = (string)sec.StartLocator.getDataItem(1);
				if (this.Storage.hasProfile(filename))
					arg.Feedback = true;
			}
		}

		public static void test() {
			SourceProfileStorage storage = new SourceProfileStorage("codeprofile.dat", "codeprofile.idx");
			CodeProfile cp = new CodeProfile(null, storage);
			StreamReader input = new StreamReader(@"d:\Documents\Activities\Trento\PhD\Experiments\Source\anno-108-new.002");
			DemlRegistry r = new DemlRegistry();
			r.loadPatterns("CvsAnnotate.xml");

			AbstractPattern cvs = r.findPattern("CVS-Annotate");
			if (cvs != null) {
				cp.processAnnotatedFile(input, cvs, null);
			}

			ISourceFileProfile pf = cp["editor/libeditor/html/nsHTMLObjectResizer.h"];
			Debug.WriteLine(pf.Filename);
			storage.Dispose();
		}

		public IEnumerator<ISourceFileProfile> GetEnumerator() {
			foreach (var item in Codebase.enumerateSourceFiles()) {
				ISourceFileProfile profile = this[item];
				if (profile != null)
					yield return profile;
			}
		}

		IEnumerator IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}
	}
}
